# -*- coding: utf-8 -*-
import geatpy as ea # 导入geatpy库
import numpy as np
from sys import path as paths
from os import path
paths.append(path.split(path.split(path.realpath(__file__))[0])[0])

class soea_GGAP_SGA_templet(ea.SoeaAlgorithm):
    
    """
soea_GGAP_SGA_templet : class - Generational Gap Simple GA templet(带代沟的简单遗传算法模板)

算法描述:
    本模板实现的是带代沟的简单遗传算法，
    它在SGA算法模板的基础上增加“代沟”，用于控制使用多少个子代替换父代来形成新一代种群，算法流程如下：
    1) 根据编码规则初始化N个个体的种群。
    2) 若满足停止条件则停止，否则继续执行。
    3) 对当前种群进行统计分析，比如记录其最优个体、平均适应度等等。
    4) 独立地从当前种群中选取N个母体。
    5) 独立地对这N个母体进行交叉操作。
    6) 独立地对这N个交叉后的个体进行变异，并根据代沟从中选择N'个个体替换父代最差的N'个个体，得到下一代种群。
    7) 回到第2步。

模板使用注意:
    本模板调用的目标函数形如：aimFunc(pop), 
    其中pop为Population类的对象，代表一个种群，
    pop对象的Phen属性（即种群染色体的表现型）等价于种群所有个体的决策变量组成的矩阵，
    该函数根据该Phen计算得到种群所有个体的目标函数值组成的矩阵，并将其赋值给pop对象的ObjV属性。
    若有约束条件，则在计算违反约束程度矩阵CV后赋值给pop对象的CV属性（详见Geatpy数据结构）。
    该函数不返回任何的返回值，求得的目标函数值保存在种群对象的ObjV属性中，
                          违反约束程度矩阵保存在种群对象的CV属性中。
    例如：population为一个种群对象，则调用aimFunc(population)即可完成目标函数值的计算，
         此时可通过population.ObjV得到求得的目标函数值，population.CV得到违反约束程度矩阵。
    若不符合上述规范，则请修改算法模板或自定义新算法模板。
    
"""
    
    def __init__(self, problem, population):
        ea.SoeaAlgorithm.__init__(self, problem, population) # 先调用父类构造方法
        if str(type(population)) != "<class 'Population.Population'>":
            raise RuntimeError('传入的种群对象必须为Population类型')
        self.name = 'GGAP-SGA'
        self.selFunc = 'rws' # 轮盘赌选择算子
        if population.Encoding == 'P':
            self.recOper = ea.Xovpmx(XOVR = 1) # 生成部分匹配交叉算子对象
            self.mutOper = ea.Mutinv(Pm = 1) # 生成逆转变异算子对象
        else:
            self.recOper = ea.Xovdp(XOVR = 1) # 生成两点交叉算子对象
            if population.Encoding == 'BG':
                self.mutOper = ea.Mutbin(Pm = 1) # 生成二进制变异算子对象
            elif population.Encoding == 'RI':
                self.mutOper = ea.Mutbga(Pm = 1, MutShrink = 0.5, Gradient = 20) # 生成breeder GA变异算子对象
            else:
                raise RuntimeError('编码方式必须为''BG''、''RI''或''P''.')
        self.GGAP = 0.9 # 代沟，表示使用多少个子代替换父代来形成新一代种群
    
    def reinsertion(self, population, offspring, GGAP_NUM):
        """ 重插入 """
        replaceIdx = np.argsort(population.FitnV.T[0])[:GGAP_NUM].astype(int) # 计算父代中要被替换的个体索引
        insertIdx = np.argsort(-offspring.FitnV.T[0])[:GGAP_NUM].astype(int) # 计算子代中需要选择进行重插入的个体索引
        population[replaceIdx] = offspring[insertIdx]
        return population
    
    def run(self):
        #==========================初始化配置===========================
        population = self.population
        NIND = population.sizes
        GGAP_NUM = int(np.ceil(NIND * self.GGAP)) # 计算每一代替换个体的个数
        self.initialization() # 初始化算法模板的一些动态参数
        #===========================准备进化============================
        if population.Chrom is None:
            population.initChrom(NIND) # 初始化种群染色体矩阵（内含染色体解码，详见Population类的源码）
        else:
            population.Phen = population.decoding() # 染色体解码
        self.problem.aimFunc(population) # 计算种群的目标函数值
        population.FitnV = ea.scaling(self.problem.maxormins * population.ObjV, population.CV) # 计算适应度
        self.evalsNum = population.sizes # 记录评价次数
        #===========================开始进化============================
        while self.terminated(population) == False:
            # 选择
            offspring = population[ea.selecting(self.selFunc, population.FitnV, NIND)]
            # 进行进化操作
            offspring.Chrom = self.recOper.do(offspring.Chrom) # 重组
            offspring.Chrom = self.mutOper.do(offspring.Encoding, offspring.Chrom, offspring.Field) # 变异
            # 求进化后个体的目标函数值
            offspring.Phen = offspring.decoding() # 染色体解码
            self.problem.aimFunc(offspring) # 计算目标函数值
            self.evalsNum += offspring.sizes # 更新评价次数
            offspring.FitnV = ea.scaling(self.problem.maxormins * offspring.ObjV, offspring.CV) # 计算适应度
            # 根据代沟把子代重插入到父代生成新一代种群
            population = self.reinsertion(population, offspring, GGAP_NUM)
            population.FitnV = ea.scaling(self.problem.maxormins * population.ObjV, population.CV) # 计算适应度
        
        return self.finishing(population) # 调用finishing完成后续工作并返回结果
    